package org.springcloud.plugin.framework.loadbalance.weight;


import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.springcloud.plugin.common.entity.WeightFilterEntity;

import java.util.ArrayList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * @Author yanggld
 * @Date 2019/12/17-18:24
 * @Description
 */
@Slf4j
public abstract class AbstractMapWeightRandomLoadBalance {
	public abstract WeightFilterEntity getT();
	public abstract int getWeight(Server server, WeightFilterEntity weightFilterEntity);

	/**
	 * 基于权重选择服务
	 * @param serverList
	 * @param ruleWeightFilterEntity
	 * @return
	 */
	public Server choose(List<Server> serverList, WeightFilterEntity ruleWeightFilterEntity){
		if (CollectionUtils.isEmpty(serverList)) {
			return null;
		}
		List<Pair<Server, Integer>> weightPairList = new ArrayList<>();
		for (Server server : serverList) {
			int weight = getWeight(server, ruleWeightFilterEntity);
			weightPairList.add(new ImmutablePair<>(server, weight));
		}
		MapWeightRandom<Server, Integer> weightRandom = new MapWeightRandom<>(weightPairList);
		return weightRandom.random();
	}

	public class MapWeightRandom<K, V extends Number> {
		private TreeMap<Double, K> weightMap = new TreeMap<Double, K>();

		public MapWeightRandom(List<Pair<K, V>> pairlist) {
			for (Pair<K, V> pair : pairlist) {
				double value = pair.getValue().doubleValue();
				if (value <= 0) {
					continue;
				}

				double lastWeight = weightMap.size() == 0 ? 0 : weightMap.lastKey().doubleValue();
				weightMap.put(value + lastWeight, pair.getKey());
			}
		}

		public K random() {
			if (MapUtils.isEmpty(weightMap)) {
				throw new RuntimeException("Weight values are all <= 0 or invalid format");
			}
			double randomWeight = weightMap.lastKey() * Math.random();
			SortedMap<Double, K> tailMap = weightMap.tailMap(randomWeight, false);
			return weightMap.get(tailMap.firstKey());
		}
	}
}
